﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Physis.Security {

    public class SecurityFactory : FactoryBase {

        #region Private Properties

        #endregion 


        #region Public Properties


        #endregion 


        #region Constructors

        public SecurityFactory (Application forApplication) : base (forApplication) { return; }

        #endregion 


        #region Factories

        public List<Security.SecurityAuthority> SecurityAuthorities (Boolean useCaching = false) {

            List<Security.SecurityAuthority> securityAuthorities = new List<Security.SecurityAuthority> ();


            ClearLastException ();

            try {

                String selectStatement = "SELECT * FROM SecurityAuthority ORDER BY Name";

                System.Data.DataTable dataTable = Application.EnterpriseDatabase.SelectDataTable (selectStatement);

                foreach (System.Data.DataRow currentDataRow in dataTable.Rows) {

                    Security.SecurityAuthority securityAuthority = new Security.SecurityAuthority (Application);

                    securityAuthority.MapDataFields (currentDataRow);

                    securityAuthorities.Add (securityAuthority);

                }

            }

            catch (Exception applicationException) {

                SetLastException (applicationException);

            }

            return securityAuthorities;

        }

        public Security.SecurityAuthority SecurityAuthority (Int64 id, Boolean useCaching = false) {

            if (id == 0) { return null; }

            String cacheKey = "Physis.Application.SecurityAuthority." + id;

            Security.SecurityAuthority securityAuthority = null;

            ClearLastException ();

            try {

                if (useCaching) { securityAuthority = (Security.SecurityAuthority)CacheManager.GetObject (cacheKey); }

                if (securityAuthority == null) {

                    securityAuthority = new Security.SecurityAuthority (Application, id);

                    CacheManager.CacheObject (cacheKey, securityAuthority, CacheExpirationData);

                }

            }

            catch (Exception applicationException) {

                SetLastException (applicationException);

            }

            return securityAuthority;

        }

        public Security.SecurityAuthority SecurityAuthority (Enumerations.SecurityAuthorityType forType, String domain, Boolean useCaching = false) {

            if (String.IsNullOrWhiteSpace (domain)) { return null; }

            String cacheKey = "Physis.Application.SecurityAuthority." + forType + "." + domain;

            Security.SecurityAuthority securityAuthority = null;

            ClearLastException ();

            try {

                if (useCaching) { securityAuthority = (Security.SecurityAuthority)CacheManager.GetObject (cacheKey); }

                if (securityAuthority == null) {

                    securityAuthority = new Security.SecurityAuthority (Application, forType, domain);

                    CacheManager.CacheObject (cacheKey, securityAuthority, CacheExpirationData);

                }

            }

            catch (Exception applicationException) {

                SetLastException (applicationException);

            }

            return securityAuthority;

        }


        public List<Security.Role> Roles (Boolean useCaching = false) {

            List<Security.Role> roles = new List<Security.Role> ();

            String cacheKey = "Application.Roles.All";

            ClearLastException ();

            try {

                if (useCaching) { roles = (List<Security.Role>)CacheManager.GetObject (cacheKey); }

                else { roles = null; }

                if (roles == null) {

                    roles = new List<Security.Role> ();

                    // THE ENVIRONMENT ID IS 0 (NULL IN THE DATABASE) FOR ENTERPRISE ROLES

                    String selectStatement = "SELECT Role.* ";

                    selectStatement += "  FROM Role";

                    selectStatement += "  ORDER BY Role.Name";


                    System.Data.DataTable dataTable = Application.EnterpriseDatabase.SelectDataTable (selectStatement);

                    foreach (System.Data.DataRow currentDataRow in dataTable.Rows) {

                        Security.Role role = new Security.Role (Application);

                        role.MapDataFields (currentDataRow);

                        CacheManager.CacheObject ("Application.Role." + role.Id, role, CacheExpirationData);  // CACHE INDIVIDUAL ROLE 

                        roles.Add (role);

                    }

                    CacheManager.CacheObject (cacheKey, roles, CacheExpirationData); // CACHE THE WHOLE LIST

                }

            }

            catch (Exception applicationException) {

                SetLastException (applicationException);

            }

            return roles;

        }

        public List<Security.Role> RolesByUserAccount (Int64 userAccountId) {

            if (userAccountId == 0) { return new List<Security.Role> (); }

            List<Security.Role> roles = new List<Security.Role> ();


            ClearLastException ();

            try {

                String selectStatement = "SELECT Role.* ";

                selectStatement += "  FROM Role JOIN RoleMembership ON Role.Id = RoleMembership.RoleId ";

                selectStatement += "  WHERE RoleMembership.UserAccountId = " + userAccountId;

                selectStatement += "  ORDER BY Role.Name";


                System.Data.DataTable dataTable = Application.EnterpriseDatabase.SelectDataTable (selectStatement);

                foreach (System.Data.DataRow currentDataRow in dataTable.Rows) {

                    Security.Role role = new Security.Role (Application);

                    role.MapDataFields (currentDataRow);

                    roles.Add (role);

                }

            }

            catch (Exception applicationException) {

                SetLastException (applicationException);

            }

            return roles;

        }

        public List<Security.Role> RolesByEnvironment (Int64 environmentId) {

            List<Security.Role> roles = new List<Security.Role> ();

            ClearLastException ();

            try {

                // THE ENVIRONMENT ID IS 0 (NULL IN THE DATABASE) FOR ENTERPRISE ROLES

                String selectStatement = "SELECT Role.* ";

                selectStatement += "  FROM Role";

                selectStatement += "  WHERE Role.EnvironmentId " + ((environmentId != 0) ? " = " + environmentId : "IS NULL");

                selectStatement += "  ORDER BY Role.Name";


                System.Data.DataTable dataTable = Application.EnterpriseDatabase.SelectDataTable (selectStatement);

                foreach (System.Data.DataRow currentDataRow in dataTable.Rows) {

                    Security.Role role = new Security.Role (Application);

                    role.MapDataFields (currentDataRow);

                    roles.Add (role);

                }

            }

            catch (Exception applicationException) {

                SetLastException (applicationException);

            }

            return roles;

        }

        public List<Security.Role> RolesByUserAccountEnvironment (Int64 userAccountId, Int64 environmentId) {

            if (userAccountId == 0) { return new List<Security.Role> (); }

            List<Security.Role> roles = new List<Security.Role> ();


            ClearLastException ();

            try {

                String selectStatement = "SELECT Role.* ";

                selectStatement += "  FROM Role JOIN RoleMembership ON Role.Id = RoleMembership.RoleId ";

                selectStatement += "  WHERE ";

                selectStatement += "    Role.EnvironmentId = " + environmentId;

                selectStatement += "    AND RoleMembership.UserAccountId = " + userAccountId;

                selectStatement += "  ORDER BY Role.Name";


                System.Data.DataTable dataTable = Application.EnterpriseDatabase.SelectDataTable (selectStatement);

                foreach (System.Data.DataRow currentDataRow in dataTable.Rows) {

                    Security.Role role = new Security.Role (Application);

                    role.MapDataFields (currentDataRow);

                    roles.Add (role);

                }

            }

            catch (Exception applicationException) {

                SetLastException (applicationException);

            }

            return roles;

        }

        public Security.Role Role (Int64 id, Boolean useCaching = false) {

            if (id == 0) { return null; }

            String cacheKey = "Physis.Application.Role." + id;

            Security.Role role = null;

            ClearLastException ();

            try {

                if (useCaching) { role = (Security.Role)CacheManager.GetObject (cacheKey); }

                if (role == null) {

                    role = new Security.Role (Application, id);

                    CacheManager.CacheObject (cacheKey, role, CacheExpirationData);

                }

            }

            catch (Exception applicationException) {

                SetLastException (applicationException);

            }

            return role;

        }

        public List<Security.RoleMembership> RoleMembership (Int64 roleId) {

            if (roleId == 0) { return new List<Security.RoleMembership> (); }

            List<Security.RoleMembership> membership = new List<Security.RoleMembership> ();


            ClearLastException ();

            try {

                String selectStatement = "SELECT RoleMembership.* ";

                selectStatement += "  FROM RoleMembership ";

                selectStatement += "  WHERE RoleId = " + roleId;

                selectStatement += "  ORDER BY UserAccountId";


                System.Data.DataTable dataTable = Application.EnterpriseDatabase.SelectDataTable (selectStatement);

                foreach (System.Data.DataRow currentDataRow in dataTable.Rows) {

                    Security.RoleMembership instance = new Security.RoleMembership (Application);

                    instance.MapDataFields (currentDataRow);

                    membership.Add (instance);

                }

            }

            catch (Exception applicationException) {

                SetLastException (applicationException);

            }

            return membership;

        }

        public List<Security.RoleMembership> RoleMembershipByUserAccountId (Int64 userAccountId, Boolean useCaching = false) {

            if (userAccountId == 0) { return new List<Security.RoleMembership> (); }

            List<Security.RoleMembership> membership = new List<Security.RoleMembership> ();

            String cacheKey = "Application.RoleMembership.ByUserAccountId." + userAccountId;

            ClearLastException ();

            try {

                if (useCaching) { membership = (List<Security.RoleMembership>)CacheManager.GetObject (cacheKey); }

                else { membership = null; }

                if (membership == null) {

                    membership = new List<Security.RoleMembership> ();


                    String selectStatement = "SELECT RoleMembership.* ";

                    selectStatement += "  FROM RoleMembership ";

                    selectStatement += "  WHERE UserAccountId = " + userAccountId;


                    System.Data.DataTable dataTable = Application.EnterpriseDatabase.SelectDataTable (selectStatement);

                    foreach (System.Data.DataRow currentDataRow in dataTable.Rows) {

                        Security.RoleMembership instance = new Security.RoleMembership (Application);

                        instance.MapDataFields (currentDataRow);

                        membership.Add (instance);

                    }

                    CacheManager.CacheObject (cacheKey, membership, CacheExpirationData); // CACHE THE WHOLE LIST

                }

            }

            catch (Exception applicationException) {

                SetLastException (applicationException);

            }

            return membership;

        }

        public List<Security.RolePermission> RolePermissions (Int64 roleId) {

            if (roleId == 0) { return new List<Security.RolePermission> (); }

            List<Security.RolePermission> rolePermissions = new List<Security.RolePermission> ();


            ClearLastException ();

            try {

                String selectStatement = "SELECT * ";

                selectStatement += "  FROM RolePermission ";

                selectStatement += "  WHERE RoleId = " + roleId;

                selectStatement += "  ORDER BY Denied, Granted";


                System.Data.DataTable dataTable = Application.EnterpriseDatabase.SelectDataTable (selectStatement);

                foreach (System.Data.DataRow currentDataRow in dataTable.Rows) {

                    Security.RolePermission rolePermission = new Security.RolePermission (Application);

                    rolePermission.MapDataFields (currentDataRow);

                    rolePermissions.Add (rolePermission);

                }

            }

            catch (Exception applicationException) {

                SetLastException (applicationException);

            }

            return rolePermissions;

        }


        public List<Security.UserAccount> UserAccounts (Boolean useCaching = false) {

            List<Security.UserAccount> userAccounts = new List<Security.UserAccount> ();


            ClearLastException ();

            try {

                String selectStatement = "SELECT * FROM UserAccount ORDER BY Name";

                System.Data.DataTable dataTable = Application.EnterpriseDatabase.SelectDataTable (selectStatement);

                foreach (System.Data.DataRow currentDataRow in dataTable.Rows) {

                    Security.UserAccount userAccount = new UserAccount (Application);

                    userAccount.MapDataFields (currentDataRow);

                    userAccounts.Add (userAccount);

                }

            }

            catch (Exception applicationException) {

                SetLastException (applicationException);

            }

            return userAccounts;

        }

        public List<Security.UserAccount> UserAccountsBySecurityAuthority (Int64 securityAuthorityId, Boolean useCaching = false) {

            List<Security.UserAccount> userAccounts = new List<Security.UserAccount> ();

            String cacheKey = "Application.UserAccounts.BySecurityAuthority." + securityAuthorityId;

            ClearLastException ();

            try {

                if (useCaching) { userAccounts = (List<Security.UserAccount>)CacheManager.GetObject (cacheKey); }

                else { userAccounts = null; }

                if (userAccounts == null) {

                    userAccounts = new List<Security.UserAccount> ();

                    String selectStatement = "SELECT * FROM UserAccount WHERE SecurityAuthorityId = " + securityAuthorityId + " ORDER BY Name";

                    System.Data.DataTable dataTable = Application.EnterpriseDatabase.SelectDataTable (selectStatement);

                    foreach (System.Data.DataRow currentDataRow in dataTable.Rows) {

                        Security.UserAccount userAccount = new UserAccount (Application);

                        userAccount.MapDataFields (currentDataRow);

                        userAccounts.Add (userAccount);

                    }

                    CacheManager.CacheObject (cacheKey, userAccounts, CacheExpirationData); // CACHE THE WHOLE LIST

                }

            }

            catch (Exception applicationException) {

                SetLastException (applicationException);

            }

            return userAccounts;

        }

        public Security.UserAccount UserAccount (Int64 id, Boolean useCaching = false) {

            if (id == 0) { return null; }

            String cacheKey = "Physis.Application.UserAccount." + id;

            Security.UserAccount userAccount = null;

            ClearLastException ();

            try {

                if (useCaching) { userAccount = (Security.UserAccount)CacheManager.GetObject (cacheKey); }

                if (userAccount == null) {

                    userAccount = new Security.UserAccount (Application, id);

                    CacheManager.CacheObject (cacheKey, userAccount, CacheExpirationData);

                }

            }

            catch (Exception applicationException) {

                SetLastException (applicationException);

            }

            return userAccount;

        }

        public List<Security.DirectoryEntryHelper> UsersBySecurityProvider (Int64 securityAuthorityId, Boolean useCaching = false) {

            String cacheKey = "Physis.Application.UsersBySecurityProvider." + securityAuthorityId;

            List<Security.DirectoryEntryHelper> users = new List<Security.DirectoryEntryHelper> ();

            SecurityAuthority securityAuthority = SecurityAuthority (securityAuthorityId, true); 
            

            if (securityAuthority == null) { return users; }

            ClearLastException ();

            try {

                users = null;

                if (useCaching) { users = (List<DirectoryEntryHelper>)CacheManager.GetObject (cacheKey); }

                if (users == null) {

                    users = securityAuthority.Users ();

                    CacheManager.CacheObject (cacheKey, users, CacheExpirationReference);

                }

            }

            catch (Exception applicationException) {

                SetLastException (applicationException);

            }

            return users;

        }


        public List<Security.Permission> Permissions (Boolean useCaching = false) {

            List<Security.Permission> permissions = new List<Security.Permission> ();

            String cacheKey = "Application.Permissions.All";

            ClearLastException ();

            try {

                if (useCaching) { permissions = (List<Security.Permission>)CacheManager.GetObject (cacheKey); }

                else { permissions = null; }

                if (permissions == null) {

                    permissions = new List<Security.Permission> ();

                    // THE ENVIRONMENT ID IS 0 (NULL IN THE DATABASE) FOR ENTERPRISE ROLES

                    String selectStatement = "SELECT Permission.* ";

                    selectStatement += "  FROM Permission";

                    selectStatement += "  ORDER BY Permission.Name";


                    System.Data.DataTable dataTable = Application.EnterpriseDatabase.SelectDataTable (selectStatement);

                    foreach (System.Data.DataRow currentDataRow in dataTable.Rows) {

                        Security.Permission permission = new Security.Permission (Application);

                        permission.MapDataFields (currentDataRow);

                        CacheManager.CacheObject ("Application.Permission." + permission.Id, permission, CacheExpirationReference);  // CACHE INDIVIDUAL ROLE 

                        permissions.Add (permission);

                    }

                    CacheManager.CacheObject (cacheKey, permissions, CacheExpirationReference); // CACHE THE WHOLE LIST

                }

            }

            catch (Exception applicationException) {

                SetLastException (applicationException);

            }

            return permissions;

        }
        
        public Security.Permission Permission (Int64 id, Boolean useCaching = false) {

            if (id == 0) { return null; }

            String cacheKey = "Physis.Application.Permission." + id;

            Security.Permission permission = null;

            ClearLastException ();

            try {

                if (useCaching) { permission = (Security.Permission)CacheManager.GetObject (cacheKey); }

                if (permission == null) {

                    permission = new Security.Permission (Application, id);

                    CacheManager.CacheObject (cacheKey, permission, CacheExpirationReference);

                }

            }

            catch (Exception applicationException) {

                SetLastException (applicationException);

            }

            return permission;

        }

        public Security.Permission Permission (String name, Boolean useCaching = false) {

            if (String.IsNullOrWhiteSpace (name)) { return null; }

            String cacheKey = "Physis.Application.Permission." + name;

            Security.Permission permission = null;

            ClearLastException ();

            try {

                if (useCaching) { permission = (Security.Permission)CacheManager.GetObject (cacheKey); }

                if (permission == null) {

                    permission = new Security.Permission (Application, name);

                    CacheManager.CacheObject (cacheKey, permission, CacheExpirationReference);

                }

            }

            catch (Exception applicationException) {

                SetLastException (applicationException);

            }

            return permission;

        }


        public List<Security.Permission> PermissionsEnterprise (Boolean useCaching = false) {

            List<Security.Permission> permissions = Permissions (useCaching);

            permissions = (from currentPermission in permissions

                           where currentPermission.Name.StartsWith ("Enterprise.")

                           orderby currentPermission.Name

                           select currentPermission).ToList ();

            return permissions;

        }

        public List<Security.Permission> PermissionsEnvironment (Boolean useCaching = false) {

            List<Security.Permission> permissions = Permissions (useCaching);

            permissions = (from currentPermission in permissions

                           where currentPermission.Name.StartsWith ("Environment.")

                           orderby currentPermission.Name

                           select currentPermission).ToList ();

            return permissions;

        }

        public List<Permission> PermissionsCalculatedForRoles (String permissionPrefix, List<Role> roles) {

            List<Permission> permissionObjects = new List<Security.Permission> ();

            List<String> permissions = new List<String>();
            
            List<String> deniedPermissions = new List<string> ();


            // CYCLE THROUGH EACH ROLE AND ACCUMULATE PERMISSIONS 

            foreach (Role currentRole in roles) {

                foreach (RolePermission currentRolePermission in currentRole.Permissions) {

                    if (currentRolePermission.Name.StartsWith (permissionPrefix)) {

                        if (currentRolePermission.Granted) {

                            if (!permissions.Contains (currentRolePermission.Name)) {

                                permissions.Add (currentRolePermission.Name);

                            }

                        } // END IF GRANTED

                        // EVALUATE DENIED INDEPENDENTLY OF THE GRANTS, DENIES ALWAYS OVERRIDE GRANTS 

                        if (currentRolePermission.Denied) {

                            if (!deniedPermissions.Contains (currentRolePermission.Name)) {

                                deniedPermissions.Add (currentRolePermission.Name);

                            }

                        } // END IF DENIED 

                    } // END IF PERMISSION 

                } // END FOR EACH ROLE PERMISSION 

            } // END FOR EACH ROLE 


            // REMOVE ALL DENIED PEMISSIONS, DENIED ALWAYS OVERRIDE GRANTS 

            foreach (String currentDeniedPermission in deniedPermissions) {

                if (permissions.Contains (currentDeniedPermission)) {

                    permissions.Remove (currentDeniedPermission);

                }

            }

            foreach (String currentGrantedPermission in permissions) {

                Permission currentPermission = Permission (currentGrantedPermission, true);

                if (currentPermission != null) {

                    permissionObjects.Add (currentPermission);

                }

            }

            return permissionObjects; 

        }

        #endregion 

    }

}
